<html><head><meta charset="utf-8"><title>Webpack 的模块热替换做了什么？-慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">

<div class="container clearfix" id="top" style="display: block; width: 1134px;">
    
    <div class="center_con js-center_con l" style="width: 1134px;">
        <div class="article-con">
                            <!-- 买过的阅读 -->
                <div class="map">
                    <a href="/read" target="_blank"><i class="imv2-feather-o"></i></a>
                    <a href="/read/29" target="_blank">Webpack 从零入门到工程化实战</a>
                    <a href="" target="_blank">
                        <span>
                            / 4-6 Webpack 的模块热替换做了什么？
                        </span>
                    </a>
                </div>

            


            <div class="art-title" style="margin-top: 0px;">
                Webpack 的模块热替换做了什么？
            </div>
            <div class="art-info">
                
                <span>
                    更新时间：2019-07-30 10:41:19
                </span>
            </div>
            <div class="art-top">
                                <img src="https://img.mukewang.com/5cd9648e0001230906400360.jpg" alt="">
                                                <div class="famous-word-box">
                    <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                    <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                    <div class="famous-word">不安于小成，然后足以成大器；不诱于小利，然后可以立远功。<p class="author">——方孝孺</p></div>
                </div>
                            </div>
            <div class="art-content js-lookimg">
                <div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">Webpack 的模块热替换（HMR - Hot Module Replacement，又称为热替换、热更新等）是 Webpack 最令人兴奋的特性之一。在没有 HMR 之前，Web 前端开发者使用类似 <a href="http://livereload.com/">LiveReload</a> 这类工具配合浏览器插件监听文件变化，然后<strong>重新加载整个页面</strong>。当 Webpack 开启了 HMR 功能之后，我们的代码修改之时，Webpack 会重新打包，并且将修改后的代码发送到浏览器，浏览器替换老的代码，保证了页面状态不会丢失，在不刷新整个页面的前提下进行局部的更新。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">所谓保证页面状态，就是在不修改页面当前状态的情况下进行替换。举例来说，当我们打开一个页面，进行了很多步的操作（比如点击了页面的一个按钮），这时候页面弹出一个弹层，我们发现弹层的背景颜色不对，这时候我们可以直接修改代码，HMR 最终会在不刷新页面的前提下直接修改弹层的背景颜色，效果跟我们在 Chrome Devtools 内修改 CSS 样式一样。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本篇文章先带领大家体验一下 HMR 的整个执行过程，然后深入到代码细节来看下 Webpack HMR 的实现原理。</p>
</div><div class="cl-preview-section"><h2 id="webpack-hmr-的流程" style="font-size: 30px;">Webpack HMR 的流程</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先我们需要创建一个 HMR 的项目，项目的文件列表如下：</p>
</div><div class="cl-preview-section"><pre><code>├── package.json
├── src
│   ├── index.html
│   ├── index.js
│   └── style.css
└── webpack.config.js
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">项目入口文件是<code>src/index.js</code>，其中引入了<code>src/style.css</code>，并且给 HTML 页面的<code>&lt;h1 id="app"&gt;</code>的增加<code>innerHTML</code>内容，具体内容如下：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token comment">// index.js</span>
<span class="token keyword">import</span> <span class="token string">'./style.css'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> $node <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'app'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
$node<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">'Hi，Webpack HMR'</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>src/style.css</code>的内容如下：</p>
</div><div class="cl-preview-section"><pre class="  language-css"><code class="prism  language-css"><span class="token selector">body </span><span class="token punctuation">{</span>
    <span class="token property">background</span><span class="token punctuation">:</span> greenyellow<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>src/index.html</code>内容如下：</p>
</div><div class="cl-preview-section"><pre class="  language-html"><code class="prism  language-html"><span class="token doctype">&lt;!DOCTYPE html&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UTF-8<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewport<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>width=device-width, initial-scale=1.0<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>X-UA-Compatible<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ie=edge<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">&gt;</span></span>Dev Server<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>app<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">&gt;</span></span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在<code>webpack.config.js</code>内容中，增加<code>html-webpack-plugin</code>和 CSS 使用到的 loader，并且设置<code>devServer.hot=true</code>开启 webpack-dev-server 的 HMR 功能，最后在插件内配上<code>HotModuleReplacementPlugin</code>，这样 Webpack 的 HMR 功能就配置完毕了：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token comment">// webpack.config.js</span>
<span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> webpack <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> HtmlWebPackPlugin <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'html-webpack-plugin'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    entry<span class="token punctuation">:</span> <span class="token string">'./src/index.js'</span><span class="token punctuation">,</span>
    devServer<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        contentBase<span class="token punctuation">:</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">'dist'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        port<span class="token punctuation">:</span> <span class="token number">9000</span><span class="token punctuation">,</span>
        hot<span class="token punctuation">:</span> <span class="token boolean">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    module<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        rules<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">{</span>test<span class="token punctuation">:</span> <span class="token regex">/\.css$/</span><span class="token punctuation">,</span> use<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">{</span>loader<span class="token punctuation">:</span> <span class="token string">'style-loader'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>loader<span class="token punctuation">:</span> <span class="token string">'css-loader'</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token punctuation">]</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    plugins<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token keyword">new</span> <span class="token class-name">HtmlWebPackPlugin</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
            template<span class="token punctuation">:</span> <span class="token string">'src/index.html'</span><span class="token punctuation">,</span>
            filename<span class="token punctuation">:</span> <span class="token string">'index.html'</span><span class="token punctuation">,</span>
            inject<span class="token punctuation">:</span> <span class="token boolean">true</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token keyword">new</span> <span class="token class-name">webpack<span class="token punctuation">.</span>HotModuleReplacementPlugin</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">配置完毕后，我们使用 npx 启动<code>webpack-dev-server --open</code>，这时候浏览器会自动打开我们的 HTML 首页：<code>http://localhost:9000</code>。我们修改<code>index.js</code>和<code>style.css</code>的内容，则浏览器的页面也进行变化，具体效果可以参考下面的 Gif 动图：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0777790001565a07370446.gif" alt="图片描述" data-original="http://img.mukewang.com/5d0777790001565a07370446.gif" class="" style="cursor: pointer;"><br>
这时候 HMR 并不是真正的局部刷新，而是重新加载，详细请看下面的 Gif 动图。注意观察 Chrome DevTools 的 Network 面板请求是重新加载的，而不是增量加载：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0777620001158503290372.gif" alt="图片描述" data-original="http://img.mukewang.com/5d0777620001158503290372.gif" class="" style="cursor: pointer;"><br>
要解决这个问题，就需要我们在<code>src/index.js</code>增加一段代码：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token comment">// index.js</span>
<span class="token comment">// 忽略之前的内容。。</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>module<span class="token punctuation">.</span>hot<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 通知 webpack改模块接受 hmr</span>
    module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>err <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Cannot apply HMR update.'</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">增加这段代码之后，再来看 HMR 的局部更新效果就实现了，具体看下面的 Gif 动图：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d07774400017ac500140014.gif" alt="图片描述" data-original="http://img.mukewang.com/5d07774400017ac500140014.gif" class="" style="cursor: pointer;"><br>
接下来我们来看下 HMR 的具体流程是怎样的。</p>
</div><div class="cl-preview-section"><h3 id="hmr-流程">HMR 流程</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">现在我们启动 <code>webpack-dev-server</code> 之后，我们先来看下 Webpack 打包的 log ：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d07772c000102a008160314.png" alt="图片描述" data-original="http://img.mukewang.com/5d07772c000102a008160314.png" class="" style="cursor: pointer;"><br>
从 log 中可以看出我们的<code>entry</code>打包出来了两个文件：<code>main.js</code>和<code>main.2cd06169aeecf9cddf61.hot-update.js</code>。这时候我们打开 Chrome 浏览器访问 <code>http://localhost:9000</code>，看下 Chrome 的 DevTools 的 Network 面板，查看页面的请求：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0777130001f40802770136.png" alt="图片描述" data-original="http://img.mukewang.com/5d0777130001f40802770136.png" class="" style="cursor: pointer;"><br>
通过观察发现，跟我们不使用 HMR 功能相比，我们页面请求由<code>index.html</code>和<code>main.js</code>两个请求（下图是没有 HMR 的正常请求），变成了 5 个请求。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0776ea00010f6701320093.png" alt="图片描述" data-original="http://img.mukewang.com/5d0776ea00010f6701320093.png" class="" style="cursor: pointer;"><br>
5 个请求的 URL 分别是：</p>
</div><div class="cl-preview-section"><pre><code>1. http://localhost:9000/
2. http://localhost:9000/main.js
3. http://localhost:9000/main.2cd06169aeecf9cddf61.hot-update.js
4. http://localhost:9000/sockjs-node/info?t=1556265072618
5. ws://localhost:9000/sockjs-node/670/rinymwto/websocket
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">5 个请求中<code>main.js</code>和<code>main.2cd06169aeecf9cddf61.hot-update.js</code>合起来是入口文件打包后的结果，这部分包含了 HMR 的 Runtime 和<code>src/index.js</code>的便以结果。其他 3 个请求中包含了一个 WebSocket 请求（<code>ws://</code>协议）。这是因为 HMR 是首先使用<code>sockjs</code>这个模块来创建一个 WebSocket 长连接来跟 Webpack 的打包服务通信的。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这时候我们修改<code>src/index.js</code>的内容：</p>
</div><div class="cl-preview-section"><pre class="  language-diff"><code class="prism  language-diff">// src/index.js
<span class="token deleted">- $node.innerHTML = 'Webpack HMR'; // 将 Webpack HMR 字符串修改为 Hi，Webpack HMR</span>
<span class="token inserted">+ $node.innerHTML = 'Hi，Webpack HMR';</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在终端中，log 发生了变化：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0776cf0001b8a708140313.png" alt="图片描述" data-original="http://img.mukewang.com/5d0776cf0001b8a708140313.png" class="" style="cursor: pointer;"><br>
生成了新的打包之后的文件，包括：<code>main.c243117d7cba3d4c4390.hot-update.js</code>和<code>c243117d7cba3d4c4390.hot-update.json</code>等。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这时候我们观察 Chrome 的请求，首先是 WebSocket 的消息中收到了一条推送内容：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0776ba00013e0003510134.png" alt="图片描述" data-original="http://img.mukewang.com/5d0776ba00013e0003510134.png" class="" style="cursor: pointer;"></p>
</div><div class="cl-preview-section"><pre><code>{"type":"hash","data":"c243117d7cba3d4c4390"}
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><strong>注意这个<code>data</code>的值，就是咱们打包后的文件名 hash</strong>！然后 Chrome 发送了两个请求：</p>
</div><div class="cl-preview-section"><pre><code>http://localhost:9000/c243117d7cba3d4c4390.hot-update.json
http://localhost:9000/main.c243117d7cba3d4c4390.hot-update.js
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0775fd0001595a06680177.png" alt="图片描述" data-original="http://img.mukewang.com/5d0775fd0001595a06680177.png" class="" style="cursor: pointer;"><br>
这两个请求的就是更新下来的更新的代码，下面我们来看下 Chrome 一开始加载的<code>main.2cd06169aeecf9cddf61.hot-update.js</code>文件和后来请求过来的<code>main.c243117d7cba3d4c4390.hot-update.js</code>内容，比较下<code>$node.innerHTML</code>内容就是我们刚刚修改<code>src/index.js</code>的内容了：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token comment">// http://localhost:9000/main.2cd06169aeecf9cddf61.hot-update.js</span>
<span class="token function">webpackHotUpdate</span><span class="token punctuation">(</span><span class="token string">'main'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token string">'./src/index.js'</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>module<span class="token punctuation">,</span> __webpack_exports__<span class="token punctuation">,</span> __webpack_require__<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token string">'use strict'</span><span class="token punctuation">;</span>
        <span class="token function">eval</span><span class="token punctuation">(</span>
            <span class="token string">"__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./style.css */ \"./src/style.css\");\n/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_css__WEBPACK_IMPORTED_MODULE_0__);\n\nconst $node = document.getElementById('app');\n$node.innerHTML = 'Webpack HMR';\n\nif (true) {\n    // 通知 webpack改模块接受 hmr\n    module.hot.accept(err =&gt; {\n        if (err) {\n            console.error('Cannot apply HMR update.', err);\n        }\n    });\n}\n\n\n//# sourceURL=webpack:///./src/index.js?"</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// http://localhost:9000/main.c243117d7cba3d4c4390.hot-update.js</span>
<span class="token function">webpackHotUpdate</span><span class="token punctuation">(</span><span class="token string">'main'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token string">'./src/index.js'</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>module<span class="token punctuation">,</span> __webpack_exports__<span class="token punctuation">,</span> __webpack_require__<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token string">'use strict'</span><span class="token punctuation">;</span>
        <span class="token function">eval</span><span class="token punctuation">(</span>
            <span class="token string">"__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./style.css */ \"./src/style.css\");\n/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_style_css__WEBPACK_IMPORTED_MODULE_0__);\n\nconst $node = document.getElementById('app');\n$node.innerHTML = 'Hi，Webpack HMR';\n\nif (true) {\n    // 通知 webpack改模块接受 hmr\n    module.hot.accept(err =&gt; {\n        if (err) {\n            console.error('Cannot apply HMR update.', err);\n        }\n    });\n}\n\n\n//# sourceURL=webpack:///./src/index.js?"</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">通过上面的全流程观察，我们可以总结出来 Webpack HMR 的全流程图如下所示：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d0775600001e23210060750.jpg" alt="图片描述" data-original="http://img.mukewang.com/5d0775600001e23210060750.jpg" class="" style="cursor: pointer;"><br>
上面的流程图展现了 HMR 的一个完整周期，整个周期分为两部分：<strong>启动阶段</strong>和<strong>文件监控更新流程</strong>。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在启动阶段，Webpack 和 webpack-dev-server 进行交互。Webpack 和 webpack-dev-server 主要是通过 Express 的中间件 <a href="https://github.com/webpack/webpack-dev-middleware">webpack-dev-middleware</a>进行交互，这个阶段可以细分为以下几个步骤：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">webpack-dev-server 启动 Webpack 打包的 watch 模式，在这种模式下 Webpack 会监听文件的变化，一旦有文件发生变化，则会重新进行打包，watch 模式下 Webpack 打包的结果不会落盘（保存到硬盘上）；</li>
<li style="font-size: 20px; line-height: 38px;">webpack-dev-server 通过 <a href="https://github.com/webpack/webpack-dev-middleware">webpack-dev-middleware</a>与 Webpack 进行交互，Webpack-dev-middleware 初始化会接收 Webpack 的 <code>Compiler</code> 对象，通过<code>Compiler</code>的钩子可以监听 Webpack 的打包过程；</li>
<li style="font-size: 20px; line-height: 38px;">如果<code>devServer.watchContentBase=true</code>，则 webpack-dev-server 监听文件夹中静态文件的变化，发生变化则通知浏览器刷新页面重新请求新的文件；</li>
<li style="font-size: 20px; line-height: 38px;">打开浏览器之后，webpack-dev-server 会利用<code>sockjs</code>在浏览器和 Server 之间创建一个 WebSocket 长连接，这个长连接是浏览器和 webpack-dev-server 的通信桥梁，它们之间的通信内容主要是传递编译模块的文件信息（hash 值），这时候如果 Webpack 监控的文件发生了修改，<code>webpack/hot/dev-server</code>来实现 HMR 更新还是刷新页面。</li>
</ol>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：</p>
<ol>
<li style="font-size: 20px; line-height: 38px;">webpack-dev-server 的 contentBase 可以理解为静态资源服务器的目录文件夹，启动 server 之后，可以通过<code>网址+电脑中文件路径</code>的方式访问到具体文件，这个文件跟 Webpack 打包出来的路径并不一样；</li>
<li style="font-size: 20px; line-height: 38px;">这里有两个文件变化的监控，第一步中 Webpack 监控整个依赖模块的文件变化，发生变化则重新出发 Webpack 编译；第三步中 webpack-dev-server 自己监控<code>contentBase</code>的文件变化，文件发生变化则通知浏览器刷新页面，这里是刷新页面并不是 HMR，这是因为<code>contentBase</code>内容是非 Webpack 打包的依赖文件。</li>
<li style="font-size: 20px; line-height: 38px;">WebSocket 需要服务端和浏览器端都有对应的创建连接代码（<code>new WebSocket</code>），webpack-dev-server 在浏览器中通过在 chunks 中插入<code>webpack-dev-server/client</code>这个文件来创建 WebSocket 通信。</li>
</ol>
</blockquote>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">到此启动阶段结束，当 Webpack 监控的文件发生变化之后，这时候就进入了文件监控更新流程，当 Webpack 监控的依赖图中的某个文件修改之后：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">Webpack 会重新编译文件，这时候我们在<code>webpack.config.js</code>中添加的插件 <code>HotModuleReplacementPlugin</code> 会生成两次编译之间差异文件列表（manifest）文件<code>[hash].hot-update.json</code>，这个 manifest JSON 文件包含了变化文件的 <strong>Update</strong> 内容，即<code>[id].[hash].hot-update.js</code>。webpack-dev-server 中的 webpack-dev-middler 会通过 Webpack 的<code>Compiler</code>钩子监听打包进程，然后通知 webpack-dev-server 使用 WebSocket 长连接推送编译之后的 hash 值；</li>
<li style="font-size: 20px; line-height: 38px;">除了发送编译后 Hash 值之外，webpack-dev-server 还会通过长连接告诉浏览器当前的页面代码是**<code>invalid</code>**状态的，需要更新新的代码；</li>
<li style="font-size: 20px; line-height: 38px;">浏览器拿到 Hash 之后，会首先发起一个 Ajax 请求 manifest 文件<code>[hash].hot-update.json</code>文件内容；</li>
<li style="font-size: 20px; line-height: 38px;">manifest 列表文件内容拿到之后，会告诉 HMR 的 Runtime 请求那些变化的 JavaScript 文件，这时候会 Runtime 会按照清单列表发起 JSONP 请求，将两次编译的差异文件<code>[id].[hash].hot-update.js</code>获取下来，插到页面<code>head</code>标签的<code>script</code>中执行，最终完成了更新的全流程。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">WebSocket 消息的含义如下图所示：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d07743900010a2108730315.png" alt="图片描述" data-original="http://img.mukewang.com/5d07743900010a2108730315.png" class="" style="cursor: pointer;"></p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：</p>
<ol>
<li style="font-size: 20px; line-height: 38px;">这里的 Hash 值为 Compilation 的 hash 值，获取 manifest 和更新文件时用的是上一次的 hash 值；</li>
<li style="font-size: 20px; line-height: 38px;">manifest 和 update 文件名可以通过 Webpack 的<code>output.hotUpdateMainFilename</code>和<code>output.hotUpdateChunkFilename</code>来设置。</li>
</ol>
</blockquote>
</div><div class="cl-preview-section"><h3 id="小结一下">小结一下</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">整个过程虽然步骤比较多，而且涉及模块比较复杂，这里先小结下：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;">webpack-dev-server：启动一个 Express Server，整合 webpack-dev-middleware 中间件、WebSocket 长连接、proxy、静态资源服务器等功能；
<ul>
<li style="font-size: 20px; line-height: 38px;">webpack-dev-middleware：跟 Webpack 进行交互，通过<code>Compiler</code>的 Hook 来监控打包流程，保证文件修改后打包结束后请求新的文件，上线一个内存型的文件系统，文件直接从内存读取可以提升 webpack-dev-server 的速度。</li>
</ul>
</li>
<li style="font-size: 20px; line-height: 38px;"><code>HotModuleReplacementPlugin</code>：插件是用来生成 HMR 的文件清单列表和差异文件的：
<ul>
<li style="font-size: 20px; line-height: 38px;">manifest 文件：JSON 文件，文件名格式为<code>[hash].hot-update.json</code>，包含所有需要更新的文件信息；</li>
<li style="font-size: 20px; line-height: 38px;">update 文件：需要更新的 JavaScript 文件，文件名格式为<code>[id].[hash].hot-update.js</code>，包含 HMR 的差异化执行代码。</li>
</ul>
</li>
</ul>
</div><div class="cl-preview-section"><h2 id="webpack-hmr-原理" style="font-size: 30px;">Webpack HMR 原理</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">HMR 的全流程我们已经梳理清楚了，下面就是 HMR 实现的技术细节了，要理解 HMR 的技术细节，只需要回答下面 4 个问题即可：</p>
</div><div class="cl-preview-section"><h3 id="webpack-dev-middleware-是怎么让-webpack-打包出来的文件存入内存的？">1. webpack-dev-middleware 是怎么让 Webpack 打包出来的文件存入内存的？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 webpack-dev-middleware 中，使用了<a href="https://github.com/webpack/memory-fs">memory-fs</a>这个内存文件系统模块，然后将 Webpack 的<code>Compiler</code>对象的<code>Compiler.outputFileSystem</code>替换掉，<code>Compiler.outputFileSystem</code>就是输出打包结果文件的：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token comment">// webpack-dev-middleware/lib/fs.js</span>
<span class="token keyword">const</span> MemoryFileSystem <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'memory-fs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    <span class="token function">setFs</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> compiler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">//.. 忽略</span>
        fileSystem <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MemoryFileSystem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        compiler<span class="token punctuation">.</span>outputFileSystem <span class="token operator">=</span> fileSystem<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="hotmodulereplacementplugin-怎么计算两次编译差量文件的？">2. HotModuleReplacementPlugin 怎么计算两次编译差量文件的？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这里 Webpack wiki 中有一张比较形象的图：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d07740a0001892e08340616.jpg" alt="图片描述" data-original="http://img.mukewang.com/5d07740a0001892e08340616.jpg" class="" style="cursor: pointer;"><br>
上图中，右边的 moduleID 为<code>4</code>和<code>9</code>的模块发生了变化，那么依赖两个模块的 4 个 Chunk 文件就需要更新，这 4 个 chunks 被放入了<code>manifest</code>JSON 文件中：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;"><code>moduleID=9</code>模块修改后：引入了新的依赖<code>12</code>，所以 <code>chunk=3</code>生成了包含<code>9</code>和<code>12</code>的 update 文件；</li>
<li style="font-size: 20px; line-height: 38px;"><code>moduleID=4</code>模块修改后：
<ul>
<li style="font-size: 20px; line-height: 38px;">去掉了 <code>chunk=4</code>的依赖，所以<code>chunk=4</code>中的<code>10</code>和<code>11</code>没有其他的模块使用，所以删除掉；</li>
<li style="font-size: 20px; line-height: 38px;">依赖<code>moduleID=4</code>模块的<code>chunk=1</code>需要更新<code>4</code>更新的内容；</li>
</ul>
</li>
<li style="font-size: 20px; line-height: 38px;">最后是入口文件<code>entry=0</code>文件依赖<code>chunks=[1,3,4]</code>需要更新。</li>
</ul>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：每个模块（module）都可以通过它的<code>parents</code>和<code>children</code>属性找到自己被谁依赖和依赖谁。</p>
</blockquote>
</div><div class="cl-preview-section"><h3 id="hmr-runtime-是根据怎样的规则去拉取新代码的？">3. HMR Runtime 是根据怎样的规则去拉取新代码的？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">HMR Runtime 指的是<code>HotModuleReplacementPlugin</code>插件中，通过<code>Compilation.mainTemplate</code>的<code>bootstrap</code>钩子注入的<code>lib/HotModuleReplacement.runtime.js</code>文件：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token comment">// webpack 4.30.0</span>
<span class="token comment">// lib/HotModuleReplacementPlugin.js</span>
<span class="token keyword">const</span> hotInitCode <span class="token operator">=</span> Template<span class="token punctuation">.</span><span class="token function">getFunctionContent</span><span class="token punctuation">(</span>
    <span class="token comment">// lib/HotModuleReplacement.runtime.js内容</span>
    <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./HotModuleReplacement.runtime'</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// hook 钩子注入</span>
mainTemplate<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>bootstrap<span class="token punctuation">.</span><span class="token function">tap</span><span class="token punctuation">(</span><span class="token string">'HotModuleReplacementPlugin'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>source<span class="token punctuation">,</span> chunk<span class="token punctuation">,</span> hash<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    source <span class="token operator">=</span> mainTemplate<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>hotBootstrap<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>source<span class="token punctuation">,</span> chunk<span class="token punctuation">,</span> hash<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> Template<span class="token punctuation">.</span><span class="token function">asString</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
        source<span class="token punctuation">,</span>
        <span class="token string">''</span><span class="token punctuation">,</span>
        hotInitCode
            <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex">/\$require\$/g</span><span class="token punctuation">,</span> mainTemplate<span class="token punctuation">.</span>requireFn<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex">/\$hash\$/g</span><span class="token punctuation">,</span> JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex">/\$requestTimeout\$/g</span><span class="token punctuation">,</span> requestTimeout<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>
                <span class="token regex">/\/\*foreachInstalledChunks\*\//g</span><span class="token punctuation">,</span>
                <span class="token function">needChunkLoadingCode</span><span class="token punctuation">(</span>chunk<span class="token punctuation">)</span>
                    <span class="token operator">?</span> <span class="token string">'for(var chunkId in installedChunks)'</span>
                    <span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`var chunkId = </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>chunk<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">;`</span></span>
            <span class="token punctuation">)</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这个 Runtime 中会调用<code>mainTemplate</code>根据不同环境注入的<code>runtime.js</code>。在浏览器环境下，注入的是<code>lib/web/JsonpMainTemplate.runtime.js</code>，这里有 2 个跟更新有关的函数，分别是：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;"><code>hotDownloadManifest</code>：发起 Ajax 请求 mainifest JSON 文件；</li>
<li style="font-size: 20px; line-height: 38px;"><code>hotDownloadUpdateChunk</code>：创建 JSONP 请求 update 文件。</li>
</ul>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">详细代码可以参见<code>lib/web/JsonpMainTemplate.runtime.js</code>内容，其他 Node.js 执行环境、WebWorker 执行环境类似。</p>
</div><div class="cl-preview-section"><h3 id="如何让自己的代码支持-hmr？">4. 如何让自己的代码支持 HMR？</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们在学习 DOM 事件的时候知道，DOM 事件会顺着 DOM 树进行冒泡。如果当前的节点不停止冒泡，则事件会往其父节点继续冒泡，一直到根节点。在 Webpack 的 HMR 处理上，也是有这个冒泡过程的。Webpack 的模块实际也有一个 module 树，module 树的根节点就是入口文件（entry），当我们一个模块代码发生了更改，就需要执行 update 事件，如果当前模块处理不了这个事件，即不知道怎么实现 HMR，那么会冒泡到父依赖节点，直到有模块可以处理 HMR 更新代码，如果到了根节点（entry）都没有处理 update 事件，就会刷新页面。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">因为我们的代码究竟能不能实现 HMR 只有编写者知道，比如我们一个 Vue 应用，修改了 Vue 组件的<code>template</code>和<code>state</code>肯定是不同的处理方式，所以不能一概而论地都给所有的 JavaScript 模块添加<code>module.hot.accept()</code>，要视情况而定。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">幸运的是大多数框架，像 React、Vue、Angular 都有自己的 HMR 工具。这些工具有的是通过 loader 的方式来实现，有的是通过 Babel 插件方式来实现的。另外要实现 Less、Sass、CSS 文件的热加载，我们可以直接使用 style-loader 来完成（生产环境打包模式下不建议使用），比如在开发模式下对于 CSS 的加载可以配置如下的 loader：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    module<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        rules<span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                test<span class="token punctuation">:</span> <span class="token regex">/\.css$/</span><span class="token punctuation">,</span>
                use<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'style-loader'</span><span class="token punctuation">,</span> <span class="token string">'css-loader'</span><span class="token punctuation">]</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">接下来介绍下如果要自己编写 HMR 的代码，那么需要用到的几个 Webpack 的扩展函数方法：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;"><code>module.hot.accept()</code></li>
<li style="font-size: 20px; line-height: 38px;"><code>module.hot.decline()</code></li>
<li style="font-size: 20px; line-height: 38px;"><code>module.hot.dispose()</code></li>
<li style="font-size: 20px; line-height: 38px;"><code>module.hot.status()</code></li>
<li style="font-size: 20px; line-height: 38px;"><code>module.hot.apply()</code></li>
</ul>
</div><div class="cl-preview-section"><h4 id="module.hot.accept函数" style="font-size: 26px;"><code>module.hot.accept()</code>函数</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>module.accept()</code>有两种使用方式，第一种是在使用的时候，声明对应依赖模块，当声明的依赖模块的更新后才会执行对应的回调，这种方式的使用方式如下：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>
    dependencies<span class="token punctuation">,</span> <span class="token comment">// 可以是一个字符串或字符串数组</span>
    callback <span class="token comment">// 用于在模块更新后触发的函数</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这里的<code>callback</code>会拿到更新的依赖模块，即<code>callback(updatedDependencies)</code>。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>accept</code>的第二种用法是自身更新：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>
    errorHandler <span class="token comment">// 在计算新版本时处理错误的函数</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在这种用法中，当前模块<strong>所有</strong>依赖的模块代码会被更新，而且这种更新不会冒泡到父级模块中去。如果此模块没有导出（<code>export</code>）的情况下有用（这是因为没有导出，所以也就没有父级调用它）。</p>
</div><div class="cl-preview-section"><h4 id="module.hot.decline函数" style="font-size: 26px;"><code>module.hot.decline()</code>函数</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>module.hot.decline()</code>函数接受一个模块依赖列表，表示拒绝这些依赖模块的更新：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">decline</span><span class="token punctuation">(</span>
    dependencies <span class="token comment">// 可以是一个字符串或字符串数组</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这时候我们在父模块可以获取到错误代码<code>decline</code>。通常这种情况下，我们没法处理 HMR 模块，只能重新加载页面。</p>
</div><div class="cl-preview-section"><h4 id="module.hot.dispose-函数" style="font-size: 26px;"><code>module.hot.dispose()</code> 函数</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>module.hot.dispose()</code>是给当前模块代码添加一个处理函数，当前模块代码被替换时会执行对应的处理函数回调。被添加的函数应该用于移除你声明或创建的任何持久资源。如果要将状态传入到更新过的模块，请添加给定 data 参数。更新后，此对象在更新之后可通过 module.hot.data 调用：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">dispose</span><span class="token punctuation">(</span>data <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 清理并将 data 传递到更新后的模块……</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">举个例子说明，我们有个模块中存在一个全局变量<code>globalId</code>。模块更新的时候，我们希望重新定义这个变量，或者将这个变量放到 <code>data</code> 中，下次可以通过<code>module.hot.data</code>进行访问，那么我们可以写如下代码：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span>module<span class="token punctuation">.</span>hot<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// dispose handler</span>
    module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">dispose</span><span class="token punctuation">(</span>data <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// 使用，这时候 globalId 是更新之前的，而不是更新之后的变量值</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>globalId<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">// 重新定义</span>
        globalId <span class="token operator">=</span> <span class="token number">999</span><span class="token punctuation">;</span>
        <span class="token comment">// 放到 data</span>
        data<span class="token punctuation">.</span>globalId <span class="token operator">=</span> globalId<span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：<code>dispose</code>是<code>addDisposeHandler</code>的 alias，所以<code>module.hot.dispose</code>效果等同于<code>module.hot.addDisposeHandler()</code>，而要移除这个回调，可以使用<code>module.hot.removeDisposeHandler(callback)</code>。</p>
</blockquote>
</div><div class="cl-preview-section"><h4 id="module.hot.status获取-hmr-状态" style="font-size: 26px;"><code>module.hot.status()</code>获取 HMR 状态</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 HMR 中，我们可以使用<code>module.hot.status()</code>获取 HMR 的状态，状态有以下几种：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;"><code>idle</code>：当前 HMR 处于空闲状态，可以调用 <code>module.hot.check</code>方法检测更新情况，调用<code>module.hot.check</code>后状态为 <code>check</code>；</li>
<li style="font-size: 20px; line-height: 38px;"><code>check</code>： HMR 正在检查模块更新，如果没有模块更新，那么重新回到 <code>idle</code> 状态。如果有更新那么会依次经过 <code>prepare</code>、<code>dispose</code>、<code>apply</code> 然后重新回到 <code>idle</code> 状态；</li>
<li style="font-size: 20px; line-height: 38px;"><code>watch</code>和<code>watch-delay</code>：<code>watch</code>表明 HMR 当前处于监听模式，可以自动接收到更新。如果接受到更新，那么就会进入 <code>watch-delay</code> 模式，然后等待机会开始更新操作。如果开始更新，那么会依次经过 <code>prepare</code>、<code>dispose</code>、<code>apply</code>状态。如果在更新的时候又监听到文件更新，那么重新回到 <code>watch</code> 或者 <code>watch-delay</code>状态；</li>
<li style="font-size: 20px; line-height: 38px;"><code>prepare</code>：表明 HMR 在准备更新。比如正在下载 Webpack 更新后的一些资源用于更新；</li>
<li style="font-size: 20px; line-height: 38px;"><code>ready</code>： 可以开始更新了，需要手动调用 <code>apply</code> 方法去继续更新操作；</li>
<li style="font-size: 20px; line-height: 38px;"><code>dispose</code>： HMR 在调用模块自己的 <code>dispose</code> 方法，并开始更新后的模块替换操作；</li>
<li style="font-size: 20px; line-height: 38px;"><code>apply</code>： HMR 在调用被替换后（<code>dispose</code>）的模块的父级模块的 <code>accept</code> 方法，当然模块自己必须能够被 <code>dispose</code>；</li>
<li style="font-size: 20px; line-height: 38px;"><code>abort</code>： 更新无法被进一步 <code>apply</code>，但是文件处于更新之前的一致状态；</li>
<li style="font-size: 20px; line-height: 38px;"><code>fail</code>： 在更新过程中抛出了异常，当前的文件状态处于不一致状态，系统需要重启。</li>
</ul>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们还可以通过注入<code>status</code>的监听回调对 HMR 的<code>status</code>进行监听：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">addStatusHandler</span><span class="token punctuation">(</span>status <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 响应当前状态……</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 移除监听</span>
module<span class="token punctuation">.</span>hot<span class="token punctuation">.</span><span class="token function">removeStatusHandler</span><span class="token punctuation">(</span>callback<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h4 id="module.hot.apply函数" style="font-size: 26px;"><code>module.hot.apply()</code>函数</h4>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>module.hot.apply()</code>函数用于主动触发更新流程，调用方式如下：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>hot
    <span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>outdatedModules <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// 过期的模块……</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>error <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// 捕获错误</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">其中 <code>options</code> 的参数有：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;"><code>ignoreUnaccepted</code>：调用 <code>accept</code> 时没有指定的模块。如果 accpet 没有参数，接受任何模块更新；</li>
<li style="font-size: 20px; line-height: 38px;"><code>ignoreDeclined</code> ：调用 <code>decline</code> 明确指定不需要检查的模块；</li>
<li style="font-size: 20px; line-height: 38px;"><code>ignoreErrored ：忽略在调用</code>accept` 时抛出的错误；</li>
<li style="font-size: 20px; line-height: 38px;"><code>onDeclined</code>：接收 <code>decline</code> 指定的模块的回调函数；</li>
<li style="font-size: 20px; line-height: 38px;"><code>onUnaccepted</code>：接收 <code>accept</code> 中没有指定的模块的回调；</li>
<li style="font-size: 20px; line-height: 38px;"><code>onAccepted</code>：接收 <code>accept</code> 中指定的模块的回调；</li>
<li style="font-size: 20px; line-height: 38px;"><code>onDisposed</code>：接收被 <code>dispose</code> 的模块的回调；</li>
<li style="font-size: 20px; line-height: 38px;"><code>onErrored</code>：接收出错的模块回调。</li>
</ul>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">每一个函数接受到的参数为如下类型：</p>
</div><div class="cl-preview-section"><pre class="  language-js"><code class="prism  language-js"><span class="token punctuation">{</span>
    type<span class="token punctuation">:</span> <span class="token string">"self-declined"</span> <span class="token operator">|</span> <span class="token string">"declined"</span> <span class="token operator">|</span>
        <span class="token string">"unaccepted"</span> <span class="token operator">|</span> <span class="token string">"accepted"</span> <span class="token operator">|</span>
        <span class="token string">"disposed"</span> <span class="token operator">|</span> <span class="token string">"accept-errored"</span> <span class="token operator">|</span>
        <span class="token string">"self-accept-errored"</span> <span class="token operator">|</span> <span class="token string">"self-accept-error-handler-errored"</span><span class="token punctuation">,</span>
    moduleId<span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span>
    <span class="token comment">// The module in question.</span>
    dependencyId<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span>
    <span class="token comment">// For errors: the module id owning the accept handler.</span>
    chain<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token comment">// For declined/accepted/unaccepted: the chain from where the update was propagated.</span>
    <span class="token comment">// 这个 chain 表示更新冒泡的顺序</span>
    parentId<span class="token punctuation">:</span> <span class="token number">5</span><span class="token punctuation">,</span>
    <span class="token comment">// For declined: the module id of the declining parent</span>
    outdatedModules<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token comment">// For accepted: the modules that are outdated and will be disposed</span>
    outdatedDependencies<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// For accepted: The location of accept handlers that will handle the update</span>
    <span class="token number">5</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    error<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token comment">// For errors: the thrown error</span>
    originalError<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token punctuation">)</span>
    <span class="token comment">// For self-accept-error-handler-errored:</span>
    <span class="token comment">// the error thrown by the module before the error handler tried to handle it.</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本文主要讲解 Webpack 的 Hot Module Replacement 流程和实现原理。先从实际项目复习了 HMR 的用法和体验了 HMR 的流程，然后详细讲解了 HMR 的整个流程中各个模块做的事情，最后在原理上通过解答 3 个问题来加深对 HMR 原理的理解。webpack-dev-server 虽然可以直接来启动 HMR，但是真正核心的是 webpack-dev-middleware。webpack-dev-server 除了这个中间件之外主要功能就是个静态服务器，而后面实战部分我会使用 Express、webpack-dev-middleware 自己来实现 「webpack-dev-server」，通过实战加深 HMR 原理 和 webpack-dev-server 功能理解。</p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">本小节 Webpack 相关面试题：</p>
<ol>
<li style="font-size: 20px; line-height: 38px;">怎么配置 Webpack 的热替换更新？</li>
<li style="font-size: 20px; line-height: 38px;">Webpack 热替换是怎么实现的？</li>
</ol>
</blockquote>
</div></div>
            </div>
                            <!-- 买过的阅读 -->
                <div class="art-next-prev clearfix">
                                                                        <!-- 已买且开放 或者可以试读 -->
                            <a href="/read/29/article/285">
                                                    <div class="prev l clearfix">
                                <div class="icon l">
                                    <i class="imv2-arrow3_l"></i>
                                </div>
                                <p>
                                    从 Webpack 的产出代码来看 Webpack 是怎么执行的
                                </p>
                            </div>
                        </a>
                                                                                            <!-- 已买且开放 或者可以试读 -->
                            <a href="/read/29/article/287">
                                                    <div class="next r clearfix">
                                <p>
                                    实战：使用 PostCSS 打造移动适配方案
                                </p>
                                <div class="icon r">
                                    <i class="imv2-arrow3_r"></i>
                                </div>

                            </div>
                        </a>
                                    </div>
                    </div>
        <div class="comments-con js-comments-con" id="coments_con">     <div class="number">精选留言 <span class="js-number">0</span></div>     <div class="comments">         <div class="input-fake js-showcommentModal">             欢迎在这里发表留言，作者筛选后可公开显示         </div>                      <div class="noData">                 <p>                     <i class="imv2-error_c"></i>                 </p>                 <p>目前暂无任何讨论</p>             </div>              </div>  </div>



    </div>
    
    
    

</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>